﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Kawanon.Foundation;
using Kawanon.Sessions;
using Kawanon.Usability;
using Kawanon.Usability.Actions;
using Kawanon.Usability.Visualization;
using Microsoft.Win32;
using QTPlugin;
using QTPlugin.Interop;

namespace Kawanon.QtHoney
{
	[Plugin(PluginType.BackgroundMultiple,
	Author = "kawanon",
	Name = "QTHoney",
	Version = "0.1.1.0",
	Description = "マウスジェスチャといくつかのボタンを追加します。")]
	public class MainPlugin : PluginClient, IBarMultipleCustomItems
	{
		[DllImport("user32.dll", SetLastError = true)]
		static extern int MapWindowPoints(IntPtr hwndFrom, IntPtr hwndTo, ref POINT lpPoints,
			[MarshalAs(UnmanagedType.U4)] int cPoints);

		[DllImport("user32.dll")]
		static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

		const uint S_OK = 0;

		private const uint FCW_STATUS = 0x0001;

		//private DebugHelpers.ConsoleAllocation alloc = new DebugHelpers.ConsoleAllocation();
		private NViewBase m_view = null;
		private ToolTip m_tooltip = null;
		private List<PluginCustomItem> m_items;
		private IEnumerable<GestureExpression> m_expressions;
		private RenderingInformation m_renderingInformation;
		private GestureInformation m_gestureInformation;
		private int[] m_orders;

		//static MainPlugin()
		//{
		//    //// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
		//    //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AppDomain_AssemblyResolve);
		//}

		// なぜか呼ばれない？（AppDomainが異なる？）
		//static Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
		//{
		//    DebugHelpers.Echo("hoge " + args.Name);

		//    Assembly res = null;
		//    string shortAssemblyName = args.Name.Split(',')[0];
		//    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		//    foreach (Assembly asm in assemblies)
		//    {
		//        if (shortAssemblyName == asm.FullName.Split(',')[0])
		//        {
		//            res = asm;
		//            break;
		//        }
		//    }
		//    return res;
		//}

		public MainPlugin()
		{
			Plugins.DebugMode = Convert.ToBoolean(Utility.GetRegistryValue("DebugMode"));
			//Console.SetOut(new StreamWriter(Stream.Null));

			m_items = new List<PluginCustomItem>(new PluginCustomItem[] {
				new PluginItems.MergeWindowsButton(),
				new PluginItems.ShowSystemMenuButton(),
				new PluginItems.ShowGestureSettingsButton(),
				new PluginItems.ExpandableGroupButton(),
				new PluginItems.SessionManagerButton()});

			m_orders = GenerateNewOrders(m_items.Count);
			int[] orders = LoadOrders();
			if (orders != null)
			{
				m_orders = ArrangeOrders(m_orders, orders);
			}

			m_expressions = GestureSettings.LoadAvailableExpressions();

			m_renderingInformation = Persistence.GetInformation<RenderingInformation>();
			m_gestureInformation = Persistence.GetInformation<GestureInformation>();
		}

		~MainPlugin()
		{
			//Echo("Destruct");
		}

		public int Count
		{
			get
			{
				return m_items.Count;
			}
		}

		public override bool HasOption
		{
			get
			{
				return true;
			}
		}

		private PluginCustomItem GetItem(int index)
		{
			//if (m_order != null)
			//{
			//    return m_items[m_order[index]];
			//}
			//else
			//{
			//    return m_items[index];
			//}
			return m_items[m_orders[index]];
		}

		public ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle, int index)
		{
			//ToolStripButton item = new ToolStripButton();
			//item.Text = "システムメニュー";
			//item.ImageScaling = ToolStripItemImageScaling.None;
			//item.AutoSize = false;
			//item.Image = GetImage(fLarge, index);
			//item.DisplayStyle = ToolStripItemDisplayStyle.Image;
			//item.Size = fLarge ? new Size(29, 31) : new Size(22, 24);
			//item.MouseDown += new MouseEventHandler(ToolStripDropDownButton_MouseDown);

			PluginCustomItem custom = GetItem(index);
			ToolStripItem item = custom.CreateItem(fLarge, displayStyle);
			return item;
		}

		private void InitializeItems()
		{
			m_items.ForEach(delegate(PluginCustomItem item)
			{
				item.Plugin = this;
				item.Server = m_server;
			});
		}

		//private void SetOrders(int[] order)
		//{
		//    List<PluginCustomItem> list = new List<PluginCustomItem>();
		//    for (int i = 0; i < order.Length; i++)
		//    {
		//        if (-1 < order[i] && order[i] < m_items.Count)
		//        {
		//            list.Add(m_items[order[i]]);
		//        }
		//    }
		//    List<PluginCustomItem> rest = m_items.FindAll(item => !list.Contains(item));
		//    list.AddRange(rest);
		//    m_items = list;
		//}

		private int[] GenerateNewOrders(int count)
		{
			int[] orders = new int[count];
			for (int i = 0; i < count; i++)
			{
				orders[i] = i;
			}
			return orders;
		}

		private int[] ArrangeOrders(int[] src, int[] orders)
		{
			List<int> rest = new List<int>(src);
			List<int> list = new List<int>();
			for (int i = 0; i < orders.Length; i++)
			{
				if (orders[i] < 0 || orders[i] >= rest.Count)
				{
					continue;
				}
				int o = rest[orders[i]];
				list.Add(o);
			}
			list.AddRange(rest.FindAll(o => !list.Contains(o)));
			return list.ToArray();
		}

		private int[] LoadOrders()
		{
			string s = Utility.GetRegistryValue("Orders") as string;
			if (s == null)
			{
				return null;
			}
			string[] list = s.Split(',');
			int[] orders = Array.ConvertAll(list, str => Int32.Parse(str));
			return orders;
		}

		private void SaveOrders(int[] orders)
		{
			string[] list = Array.ConvertAll(orders, i => i.ToString());
			string s = String.Join(",", list);

			Utility.SetRegistryValue("Orders", s);
		}

		public void Initialize(int[] order)
		{
			//string s = (order == null) ? "null" : String.Join(",", Array.ConvertAll(order, i => i.ToString()));
			//Dbg.Echo(s);

			if (order == null)
			{
				return;
			}
			m_orders = ArrangeOrders(m_orders, order);

			SaveOrders(m_orders);
		}

		public string GetName(int index)
		{
			return GetItem(index).Name;
		}

		public Image GetImage(bool fLarge, int index)
		{
			return GetItem(index).GetImage(fLarge);
		}

		public override void OnOption()
		{
			Control band = Utility.GetBandObject(m_server);

			//Control.CheckForIllegalCrossThreadCalls = true;
			
			//if (band.InvokeRequired)
			//{
			//    Dbg.Echo("un");
			//}

			//NOTE: そのままFormを開くとオプションダイアログと同じスレッドで実行され、
			// PropertyGrid の ColorEditor 内でスレッド関連のエラーが出てしまう
			// ので、無理やりメインスレッド側で開く
			Utility.DoModal(m_server, delegate()
			{
				band.Invoke((MethodInvoker)delegate()
				{
					OptionsForm form = new OptionsForm(m_server);
					form.StartPosition = FormStartPosition.CenterScreen;
					form.Owner = Form.ActiveForm;
					form.Visible = true;
				});

				return DialogResult.OK;
			});

			//DebugForm f2 = new DebugForm();
			//f2.PropertyGrid.SelectedObject = DynamicReflectorFactory.Create(GetBandObject());
			//f2.Show();
		}

		IShellView GetActiveShellView()
		{
			if (m_browser == null)
			{
				Dbg.Abort("m_browser == null");
				return null;
			}
			IShellView view;
			if (m_browser.QueryActiveShellView(out view) == S_OK)
			{
				return view;
			}
			return null;
		}

		private IntPtr GetActiveViewWindow()
		{
			IShellView view = GetActiveShellView();
			if (view == null)
			{
				// ウィンドウがまだ開いてない
				return IntPtr.Zero;
			}

			IntPtr hwnd = IntPtr.Zero;
			if (view.GetWindow(out hwnd) != S_OK)
			{
				Dbg.Abort("GetWindow");
				return IntPtr.Zero;
			}
			return hwnd;
		}

		private bool BindView(NViewBase view, IntPtr hwnd)
		{
			if (NativeWindow.FromHandle(hwnd) != null)	// すでにNativeWindowにバインドされてないか調べる
			{
				throw new AlreadyAttachedException();
			}

			IntPtr hwndStatusBar = IntPtr.Zero;
			if (m_browser.GetControlWindow(FCW_STATUS, out hwndStatusBar) != S_OK)
			{
				Dbg.Abort("GetControlWindow Failed");
			}

			view.Server = m_server;
			view.Plugin = this;
			view.StatusBarHandle = hwndStatusBar;
			view.ShellView = GetActiveShellView();
			if (m_gestureInformation.DrawTrails)
			{
				view.DrawTrails = true;
				view.Renderer = TrailRendererFactory.CreateInstance(m_renderingInformation);
			}
			view.Expressions = m_expressions;
			view.GestureInformation = m_gestureInformation;

			m_view = view;	// 参照を保持しておく
			m_view.AssignHandle(hwnd);
			return true;
		}

		private bool AttachToListView(IntPtr hwndView)
		{
			IntPtr listview = Utility.FindChildWindow(hwndView, Utility.EqualsClassName("SysListView32"));
			if (listview == IntPtr.Zero)
			{
				//NOTE: IEエンジンなど
				throw new WindowNotFoundException();
			}

			return BindView(new NListView(), listview);
		}

		private bool AttachToWebBrowser(IntPtr hwndView)
		{
			IntPtr hwnd = Utility.FindChildWindow(hwndView, Utility.EqualsClassName("Internet Explorer_Server"));
			if (hwnd == IntPtr.Zero)
			{
				throw new WindowNotFoundException();
			}

			return BindView(new NWebBrowser(), hwnd);
		}

		private bool AttachView_Internal()
		{
			IntPtr hwndView = GetActiveViewWindow();
			if (hwndView == IntPtr.Zero)
			{
				return false;
			}
			try
			{
				return AttachToListView(hwndView);
			}
			catch (WindowNotFoundException)
			{
				try
				{
					return AttachToWebBrowser(hwndView);
				}
				catch (WindowNotFoundException)
				{
					Dbg.Abort("WebBrowser Not Found");
					return false;
				}
			}
		}

		private void HideBalloonTip()
		{
			if (m_tooltip == null)
			{
				return;
			}
			Control band = Utility.GetBandObject(m_server);
			m_tooltip.Hide(band);
			//Dbg.Play();
		}

		private void ShowBalloonTip(IntPtr hwnd)
		{
			HideBalloonTip();
			if (m_tooltip == null)
			{
				m_tooltip = new ToolTip();
			}
			m_tooltip.IsBalloon = true;
			m_tooltip.ToolTipTitle = "このビューではマウスジェスチャは使えません。";
			m_tooltip.ToolTipIcon = ToolTipIcon.Warning;

			Control band = Utility.GetBandObject(m_server);
			RECT rc = new RECT();
			if (!GetClientRect(hwnd, out rc))
			{
				Dbg.Abort("GetWindowRect Failed");
			}
			POINT pt = new POINT(new Point(rc.bottom / 2, rc.bottom / 2));
			if (MapWindowPoints(hwnd, band.Handle, ref pt, 1) == 0)
			{
				Dbg.Abort("MapWindowPoints Failed");
			}
			try
			{
				//NOTE: window は内部で Control にキャストされるため、ここでは QTTabBar を親に使う
				m_tooltip.Show("Coming soon...", band, pt.x, pt.y, 5 * 1000);
			}
			catch (Exception ex)
			{
				Dbg.Echo(ex);
			}
			//Dbg.Echo(pt.x +" "+ pt.y);
		}

		public bool AttachView()
		{
			HideBalloonTip();

			if (!m_gestureInformation.Enabled)
			{
				return false;
			}

			bool ret = AttachView_Internal();
			if (!ret)
			{
				IntPtr hwndStatusBar = IntPtr.Zero;
				if (m_browser.GetControlWindow(FCW_STATUS, out hwndStatusBar) != S_OK)
				{
					Dbg.Abort("GetControlWindow Failed");
				}
				if (hwndStatusBar != IntPtr.Zero)
				{
					ShowBalloonTip(hwndStatusBar);
				}
			}
			return ret;
		}

		public bool DetachView()
		{
			if (m_view != null)
			{
				m_view.ReleaseHandle();
				m_view = null;
				return true;
			}
			else
			{
				return false;
			}

			//Play();
		}

		public override void OnMenuItemClick(MenuType menuType, string menuText, ITab tab)
		{
			//Utility.ShowForm(m_server, new GestureForm(m_server));
			Utility.DoModal(m_server, new GestureForm(m_server));
		}

		private void LoadSettings()
		{
			m_server.RegisterMenu(this, MenuType.Bar, "マウスジェスチャの設定",
				m_gestureInformation.Enabled);
		}

		protected override void OnOpen()
		{
			//Dbg.Echo(m_server.GetHashCode());

			//if (PluginInformation.Server != null && PluginInformation.Server != m_server)
			//{
			//    // ここで強制終了することがある...
			//    Dbg.Abort("another PluginInformation.Server already exists");
			//}
			//PluginInformation.Server = m_server;

			Plugins.Instances.Add(this);

			InitializeItems();

			m_server.NavigationComplete += new PluginEventHandler(PluginServer_NavigationComplete);
			Plugins.SettingsChanged += new EventHandler(Plugins_SettingsChanged);

			AttachView();

			LoadSettings();
		}

		void Plugins_SettingsChanged(object sender, EventArgs e)
		{
			Plugins.DebugMode = Convert.ToBoolean(Utility.GetRegistryValue("DebugMode"));
			//Dbg.Echo(Plugins.DebugMode);

			m_expressions = GestureSettings.LoadAvailableExpressions();
			m_renderingInformation = Persistence.GetInformation<RenderingInformation>();
			m_gestureInformation = Persistence.GetInformation<GestureInformation>();

			DetachView();
			AttachView();

			LoadSettings();
		}

		void PluginServer_NavigationComplete(object sender, PluginEventArgs e)
		{
			try
			{
				AttachView();
			}
			catch (AlreadyAttachedException)
			{
				//NOTE: 新規ウィンドウで新しく QTTabBar バンドを開いた時は、
				//      IShellBrowser のアクティブビューがまだ切り替わっていない
			}
		}

		public override void Close(EndCode endCode)
		{
			if (endCode == EndCode.Hidden)
			{
				//Dbg.Trace("EndCode Hidden");
				return;
			}

			DetachView();

			//m_items.Clear();

			if (!Plugins.Instances.Remove(this))
			{
				Dbg.Abort("not registered in PluginInformation.Instances");
			}

			m_server.NavigationComplete -= new PluginEventHandler(PluginServer_NavigationComplete);
			Plugins.SettingsChanged -= new EventHandler(Plugins_SettingsChanged);

			//Echo("Close");
		}

		public static void Uninstall()
		{
			if (Utility.IsDebugMode())
			{
				return;
			}
			if (Plugins.OpenRegistryKey(false) == null)
			{
				return;
			}

			string text = String.Format("{0} の設定をすべて削除しますか？",
				Assembly.GetExecutingAssembly().ManifestModule.ScopeName);	// Name は<不明>になる
			DialogResult res = MessageBox.Show(text, "確認",
				MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
			if (res != DialogResult.Yes)
			{
				return;
			}

			string[] ns = typeof(MainPlugin).Namespace.Split('.');
			RegistryKey key = Registry.CurrentUser.OpenSubKey(
				String.Format(@"Software\Quizo\QTTabBar\Plugins\Settings\{0}", ns[0]), true);
			if (key == null)
			{
				return;
			}
			key.DeleteSubKeyTree(ns[1]);
			key.Flush();
			key.Close();
		}

	}

}
